AI pair programming &
LLMs chats in Emacs

noshadow

Marie-Hélène Burle

October 7, 2025


Warning

I normally don’t share my Emacs init code because it relies on a remapping of the semi-colon as a ring-map which makes my keybindings absurd on another machine. I also use straight.el for packages installations and it itself requires to be installed first

But I keep being asked for my code after each webinar. So this time, I am sharing it

Don’t copy-paste it in your init file: it would break your Emacs. Instead, use it to inspire your potential setup or, better still, go to the packages READMEs. They provide a much better place to start

You have been warned… 🙂

Safe API key storage

Your LLMs API keys, login credentials, or passwords should never appear as plain text in your init file

Some packages provide mechanisms for safe storing or logging

Others don’t. In that case, there are multiple options. My favourites are based on auth-source and GPG

Safe API key storage: authinfo.gpg

Create a ~/.authinfo.gpg file with your keys in the format:

machine <hostname> login <username> password <password>

If you need a function to retrieve your key, use:

(lambda ()
  (auth-source-pick-first-password
    :host "<hostname>"
    :user "<username>"))

If you need a string, simply use:

(auth-source-pick-first-password
  :host "<hostname>"
  :user "<username>")

Safe API key storage: pass

Use the standard Unix password manager to store your API key by running in a Unix shell:

pass insert <key-name>
# enter your API key twice when prompted

Function:

(lambda ()
  (auth-source-pass-get 'secret "<key-name>"))

String:

(auth-source-pass-get 'secret "<key-name>")

copilot.el

GitHub copilot code completion
(built on top of the GitHub copilot-language-server)

Requirements

Access to GitHub copilot

Free tier available to everybody
Pro available free for students, teachers, maintainers of popular open-source projects
Paid subscriptions for Pro and Pro+

Emacs ≥ 27

Node.js ≥ 22

Installation

Install and load the package and dependency with your favourite method

Install the copilot server with M-x copilot-install-server

Login to Copilot with M-x copilot-login and follow the instructions

You can test the setup with M-x copilot-diagnose

My setup (see warning)

;; dependency
(straight-use-package 'editorconfig)

(use-package copilot
    :straight (:host github
               :repo "copilot-emacs/copilot.el"
               :files ("dist" "*.el"))
    :bind (("C-8" . copilot-complete)
           ("; j c" . copilot-mode)
           :map copilot-completion-map
           ("C-j" . copilot-accept-completion)
           ("C-f" . copilot-accept-completion-by-word)
           ("C-t" . copilot-accept-completion-by-line)
           ("M-n" . copilot-next-completion)
           ("M-p" . copilot-previous-completion)))

copilot-chat.el

GitHub copilot chat

Requirements

Access to GitHub copilot

Functionality

Chat with a model

Markdown or Org markup
Chats can be saved and restored
Buffers can be added or removed as context
Can choose model

AI pair programming

Write tests
Explain code/function at point/symbol at point
Review code
Document code
Fix code
Optimize code
Modify code
Customize prompts

Generate commit messages

My setup (see warning)

;; dependency
(straight-use-package 'magit)

(use-package copilot-chat
  :straight (:host github:repo "chep/copilot-chat.el" :files ("*.el"))
  :after (org markdown-mode)
  :bind (("; C" . copilot-chat)
         ("; c y" . copilot-chat-yank)
         ("; c m" . copilot-chat-set-model)
         :map prog-mode-map
         ;; explain symbol under point
         ("; p e s" . copilot-chat-explain-symbol-at-line)
         ;; explain function under point
         ("; p e f" . copilot-chat-explain-defun)
         ;; explain selected code
         ("; p e c" . copilot-chat-explain)
         ;; review selected code
         ("; p r c" . copilot-chat-review)
         ;; review current buffer
         ("; p r b" . copilot-chat-review-whole-buffer)
         ;; document selected code
         ("; p d c" . copilot-chat-doc)
         ;; fix selected code
         ("; p f c" . copilot-chat-fix)
         ;; optimize selected code
         ("; p o c" . copilot-chat-optimize)
         ;; write tests for selected code
         ("; p t c" . copilot-chat-test)
         ;; apply a custom prompt to the function body under point
         ;; (instruct on how to refactor the function)
         ("; p c f" . copilot-chat-custom-prompt-function)
         :map copilot-chat-org-prompt-mode-map
         ("C-<return>" . copilot-chat-prompt-send)
         :map org-mode-map
         ("; p l" . copilot-chat-prompt-split-and-list)))

gptel

Access LLMs from any buffer

Requirements

API key(s) for model(s), GitHub copilot, or model(s) running locally

Functionality

Chat in dedicated buffer or from any buffer

Markdown or Org markup
Choose model
Add/remove context (including media)
Set temperature

A number of packages are built on top of gptel

My setup (see warning)

(use-package gptel
  :config
  (setq
   gptel-model 'gemini-2.5-pro
   gptel-backend (gptel-make-gemini "Gemini"
                   :key #'gptel-api-key-from-auth-source
                   :stream t))
  :bind (("; g g" . gptel)
         ("; g s" . gptel-send)
         ("; g m" . gptel-menu)))

chatgpt-shell

A shell to access LLMs

Requirements

API key(s) for model(s) or local model(s)

Functionality

Chat in an Emacs shell

Choose and swap models
Describe code
Proofread text
Write commits messages
Save/restore transcripts

My setup (see warning)

;; dependency
(use-package shell-maker
  :straight (:type git :host github :repo "xenodium/shell-maker"))

(use-package chatgpt-shell
  :straight (:type git :host github
             :repo "xenodium/chatgpt-shell"
             :files ("chatgpt-shell*.el"))
  :init
  (setq chatgpt-shell-google-key
    (lambda ()
      (auth-source-pick-first-password
       :host "google_api_key" :user "secret")))
  :bind ("; c s" . chatgpt-shell))

aidermacs

aider in Emacs
(forget about Cursor)

Requirements

API key(s) for model(s) or local model(s)

Emacs ≥ 26.1

aider

transient

Functionality

Ediff of AI-generated changes
Custom prompts
Code/chat/help/architect modes
Integrates with vterm
Auto-detects project root
Voice commands
Retrieves web content
Writes tests
Debugs code
Weak model for fast easy tasks
TRAMP support
Easy passing of aider options

My setup (see warning)

(use-package aidermacs
  :bind (("; c a" . aidermacs-transient-menu))
  :config
  (setenv "GOOGLE_API_KEY" (auth-source-pick-first-password
                :host "google_api_key"
                :user "secret"))
  :custom
  (aidermacs-default-chat-mode 'architect)
  (aidermacs-default-model "gemini"))

Alternative: aider.el

aider.el is the initial project aidermacs was forked from